/*
 * Copyright 2007-2008 Andrew O'Malley
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package ws.quokka.plugin.standard_lifecycle;

import org.apache.tools.ant.taskdefs.Copy;
import org.apache.tools.ant.taskdefs.Delete;
import org.apache.tools.ant.util.FileUtils;

import ws.quokka.core.bootstrap_util.IOUtils;
import ws.quokka.core.bootstrap_util.QuokkaEntityResolver;
import ws.quokka.core.metadata.Metadata;
import ws.quokka.core.metadata.MetadataAware;
import ws.quokka.core.repo_spi.RepoArtifact;
import ws.quokka.core.repo_spi.RepoArtifactId;
import ws.quokka.core.repo_spi.RepoXmlConverter;
import ws.quokka.core.repo_spi.Repository;
import ws.quokka.core.repo_spi.RepositoryAware;
import ws.quokka.core.repo_spi.RepoPath;
import ws.quokka.core.util.xml.Document;

import ws.quokka.plugin.lifecycle.LifeCycleImpl;

import java.io.File;

import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;


/**
 *
 */
public class StandardLifeCyclePlugin extends LifeCycleImpl implements MetadataAware, RepositoryAware {
    //~ Instance fields ------------------------------------------------------------------------------------------------

    private Metadata metadata;
    private Repository repository;
    private IOUtils utils = new IOUtils();

    //~ Methods --------------------------------------------------------------------------------------------------------

    public void setMetadata(Metadata metadata) {
        this.metadata = metadata;
    }

    public void setRepository(Repository repository) {
        this.repository = repository;
    }

    public void clean() {
        // Delete the target directory
        Delete delete = (Delete)getProject().createTask("delete");
        delete.setDir(getTargetDir());
        delete.perform();
    }

    public void install() {
        // Installation is the same for all artifacts.
        for (Iterator i = metadata.getArtifactIds().iterator(); i.hasNext();) {
            RepoArtifactId artifactId = (RepoArtifactId)i.next();
            File artifactPackage = getPackageFile(artifactId);

            if (artifactPackage.exists()) {
                File repositoryFile = getRepositoryFile(artifactId);
                RepoArtifact artifact = new RepoArtifact();

                if (repositoryFile.exists()) {
                    artifact = parseArtifact(repositoryFile);
                }

                if (artifactId.getVersion().isSnapShot()) {
                    artifact.setTimestamp(new Date()); // Timestamp the snapshot
                }

                artifact.setId(artifactId);
                artifact.setLocalCopy(artifactPackage);
                repository.install(artifact);
            }
        }
    }

    public void packageIt() {
        utils().mkdir(getLifeCycle().getPackageOutput());

        // Create the repository.xml files for the artifacts
        for (Iterator i = metadata.getExportedArtifacts().iterator(); i.hasNext();) {
            RepoArtifact artifact = (RepoArtifact)i.next();
            createRepositoryXml(artifact, getRepositoryFile(artifact.getId()));
        }

        // Copy any licenses defined locally
        for (Iterator i = metadata.getLocalLicenses().entrySet().iterator(); i.hasNext();) {
            Map.Entry entry = (Map.Entry)i.next();
            RepoArtifactId id = (RepoArtifactId)entry.getKey();
            File file = (File)entry.getValue();
            file = getProject().resolveFile(getProject().replaceProperties(file.getPath()));

            File artifactPackage = getPackageFile(id);
            Copy copy = (Copy)getProject().createTask("copy");
            copy.setFile(file);
            copy.setTofile(artifactPackage);
            copy.execute();
        }
    }

    public void generateResources() {
        for (Iterator i = metadata.getArtifactIds().iterator(); i.hasNext();) {
            RepoArtifactId id = (RepoArtifactId)i.next();

            Properties properties = generateProperties(id);

            // Compare properties to any existing properties and save if changed
            File file = getArtifactProperties(id);

            if (file.exists()) {
                Properties existing = utils.loadProperties(file);

                if (existing.equals(properties)) {
                    log().verbose("Skipping generation of properties as the contents are unchanged: "
                        + file.getAbsolutePath());

                    continue;
                }
            }

            log().info("Generated artifact properties to " + file.getAbsolutePath());
            utils().mkdir(file.getParentFile());
            utils.saveProperties(file, properties);
        }

        for (Iterator i = metadata.getExportedArtifacts().iterator(); i.hasNext();) {
            RepoArtifact artifact = (RepoArtifact)i.next();
            File file = utils().normalise(getLifeCycle().getResourcesOutput().getAbsolutePath() + "/META-INF/quokka/"
                    + artifact.getId().toPathString() + "/repository.xml");
            createRepositoryXml(artifact, file);
        }
    }

    private Properties generateProperties(RepoArtifactId id) {
        Properties properties = new Properties();
        putId(properties, id, "artifact");

        StringBuffer pathsList = new StringBuffer();
        Map paths = metadata.getExportedPaths(id);

        for (Iterator j = paths.entrySet().iterator(); j.hasNext();) {
            Map.Entry entry = (Map.Entry)j.next();
            String from = (String)entry.getKey();
            String to = (String)entry.getValue();
            List path = metadata.getProjectPath(from);
            int count = 0;

            for (Iterator k = path.iterator(); k.hasNext();) {
                RepoArtifact artifact = (RepoArtifact)k.next();
                putId(properties, artifact.getId(), "path." + to + "." + count++);
            }

            pathsList.append(to);

            if (j.hasNext()) {
                pathsList.append(", ");
            }
        }

        properties.put("paths", pathsList.toString());

        return properties;
    }

    private void putId(Properties properties, RepoArtifactId id, String prefix) {
        properties.put(prefix + ".id.group", id.getGroup());
        properties.put(prefix + ".id.name", id.getName());
        properties.put(prefix + ".id.type", id.getType());
        properties.put(prefix + ".id.version", id.getVersion().toString());
    }

    private void createRepositoryXml(RepoArtifact artifact, File repositoryFile) {
        utils().mkdir(repositoryFile.getParentFile());

        if (repositoryFile.exists()) {
            RepoArtifact existing = parseArtifact(repositoryFile);

            // Parsing automatically adds a default runtime path if none are defined ...
            if (artifact.getPaths().size() == 0 && artifact.getDependencies().size() == 0 && existing.getDependencies().size() == 0
                    && existing.getPaths().size() == 1 && existing.getPath("runtime") != null) {
                artifact.addPath(new RepoPath("runtime", "Runtime path", true, true));
            }

            if (existing.equals(artifact)) {
                log().verbose("Skipped repository file generation as it exists and is current for "
                    + artifact.getId().toShortString() + ": " + repositoryFile.getPath());

                return;
            }

            if (log().isDebugEnabled()) {
                // Helps determine why it's been regenerated
                log().debug("existing: " + existing);
                log().debug("     new: " + artifact);
            }
        }

        log().info("Generated repository file for " + artifact.getId().toShortString() + ": "
            + repositoryFile.getPath());

        RepoXmlConverter.toXml(artifact, repositoryFile);
    }

    private RepoArtifact parseArtifact(File repositoryFile) {
        QuokkaEntityResolver resolver = new QuokkaEntityResolver();
        resolver.addVersion("repository", new String[] { "0.1", "0.2" });

        return (RepoArtifact)RepoXmlConverter.getXmlConverter().fromXml(RepoArtifact.class,
            Document.parse(repositoryFile, resolver).getRoot());
    }

    private File getArtifactProperties(RepoArtifactId id) {
        return FileUtils.getFileUtils().normalize(new File(getLifeCycle().getResourcesOutput(),
                "META-INF/quokka/" + id.getGroup() + "_" + id.getName() + "_" + id.getType() + "_artifacts.properties")
            .getPath());
    }

    private File getRepositoryFile(RepoArtifactId id) {
        return new File(getLifeCycle().getPackageOutput(), id.toPathString() + "_repository.xml");
    }

    private File getPackageFile(RepoArtifactId id) {
        return new File(getLifeCycle().getPackageOutput(),
            id.toPathString() + "." + metadata.getType(id.getType()).getExtension());
    }
}
